package com.fsy.its.netty.dtu.decoder;

import com.fsy.its.entity.Dtu;
import com.fsy.its.netty.config.DtuDecoder;
import com.fsy.its.netty.dtu.DtuIdDecoder;
import com.fsy.its.netty.dtu.NoneMsgEncoder;
import com.fsy.its.netty.msg.FireObjectInfo;
import com.fsy.its.netty.msg.FireStateMsg;
import com.fsy.its.netty.msg.MsgHead;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.io.UnsupportedEncodingException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * 三江m210报警主机打印口解码
 */
@Slf4j
@DtuDecoder(brand="Sanjiang", model="m210_print")
public class SanjiangPrintDecoder extends DelimiterBasedFrameDecoder {

    private static final byte[] FRAME_START = {0x1B,0x40};
    private static final byte[] FRAME_END = {0x0D,0x0A};
    private static final int FRAME_MAX_LEN = 1024;
    private static final int FRAME_MIN_LEN = 18;

    @Autowired
    private ApplicationContext applicationContext;

    public SanjiangPrintDecoder() {
        super(FRAME_MAX_LEN, Unpooled.copiedBuffer(FRAME_START), Unpooled.copiedBuffer(FRAME_END));
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        ByteBuf frame = (ByteBuf)super.decode(ctx, buffer);
        if( frame == null ) {
            return null;
        }
        if( frame.readableBytes() < FRAME_MIN_LEN ) {
            frame.skipBytes(frame.readableBytes());
            return null;
        }
        ByteBuf frameCopied = frame.slice();

        FireStateMsg fireStateMsg = new FireStateMsg();
        fireStateMsg.head.setCommand(MsgHead.CMD_SEND);
        Attribute<Dtu> attr = ctx.channel().attr(DtuIdDecoder.DTU_KEY);
        fireStateMsg.head.setSrcAddr(attr.get().getId());

        //第一行内容为：告警日期 告警时间 设备编号
        String line = getLine(frame);
        if( line.length()==0 ) {
            log.error("No time and device number info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        //告警日期、告警时间和设备编号依次以空格分隔
        String[] ss = line.split("\\s+");
        if( ss.length < 3 ) {
            log.error("Invalid time and device number info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        //日期和时间的格式分别为：2020.02.08   15:34:01
        fireStateMsg.head.time = LocalDateTime.of(
                LocalDate.parse(ss[0], DateTimeFormatter.ofPattern("yyyy.MM.dd")),
                LocalTime.parse(ss[1], DateTimeFormatter.ISO_LOCAL_TIME)
        );
        fireStateMsg.component = new FireObjectInfo();
        fireStateMsg.component.addr = ss[2];

        /**
         * 第二行：设备类型
         */
        line = getLine(frame);
        if( line.length()==0 ) {
            log.error("No device type info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        ss = line.split("\\s+");
        if( ss.length==0 ) {
            log.error("No device type info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        //如果有两块内容（空格分隔），第一块内容为设备描述的后半部分信息
        String deviceDesc = null;
        if( ss.length >=2 ) {
            fireStateMsg.component.typeName = ss[1];
            fireStateMsg.component.type = deviceTypeByName(ss[1]);
            deviceDesc = ss[0];
        }
        else {
            fireStateMsg.component.typeName = ss[0];
            fireStateMsg.component.type = deviceTypeByName(ss[0]);
        }

        //第三行：火警或故障
        line = getLine(frame);
        if( line.length()==0 ) {
            log.error("No alarm or default info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        switch (line) {
            case "火警":
                fireStateMsg.alarm = 1;
                break;
            case "故障":
                fireStateMsg.fault = 1;
                break;
            case "故障消除":
                fireStateMsg.fault = -1;
                break;
        }

        //第四行：设备描述
        line = getLine(frame);
        if(line.length() == 0) {
            log.error("No device description info.\r\n{}", ByteBufUtil.hexDump(frameCopied));
            return null;
        }
        if( deviceDesc != null ) {
            fireStateMsg.component.desc = line + deviceDesc;
        }
        else {
            fireStateMsg.component.desc = line;
        }

        return fireStateMsg;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        ctx.pipeline().addBefore("handler", "noneEncoder", applicationContext.getBean(NoneMsgEncoder.class));
    }

    /**
     * 从帧数据里获取一行内容
     * 行以0x1C26打印控制指令分隔，每行有效内容到0x1C或0x1B控制指令截止
     * 每行内容是以gbk编码的字符串
     * @param frame
     * @return
     * @throws UnsupportedEncodingException
     */
    private String getLine(ByteBuf frame) throws UnsupportedEncodingException {
        //行以0x1C26结束，如果没有找到0x1C26则缓冲器的所有内容就是一行
        final ByteBuf lineFlag = Unpooled.copiedBuffer(new byte[]{0x1C, 0x26});
        int index = ByteBufUtil.indexOf(lineFlag, frame);
        ByteBuf line;
        if( index < 0 ) {
            line = frame.readSlice(frame.readableBytes());
        }
        else {
            line = frame.readSlice(index - frame.readerIndex());
            frame.skipBytes(lineFlag.capacity());
        }

        //查找0x1C和0x1B控制指令，找出行里的有效内容
        int flagIndex = line.indexOf(line.readerIndex(), line.writerIndex(), (byte)0x1C);
        int flag2Index = line.indexOf(line.readerIndex(), line.writerIndex(), (byte)0x1B);
        if( flag2Index > 0 && flagIndex>0 ) {
            flagIndex = flag2Index<flagIndex?flag2Index:flagIndex;
        }
        else if( flag2Index > 0 ) {
            flagIndex = flag2Index;
        }

        if( flagIndex > 0 ) {
            return new String(ByteBufUtil.getBytes(line, line.readerIndex(), flagIndex),"gbk");
        }
        else {
            return new String(ByteBufUtil.getBytes(line, line.readerIndex(), line.writerIndex()),"gbk");
        }
    }

    private short deviceTypeByName(String typeName) {
        switch(typeName) {
            case "手动按钮":
                return FireObjectInfo.Com_ShouDongAnNiu;
            case "压力开关":
                return FireObjectInfo.Com_YaLiKaiGuan;
        }
        return FireObjectInfo.Com_Unknown;
    }
}
